<template>
  <div class="x6-vue-wrap">
    <div class="header">
      <span class="text">x6-vue-example</span>
    </div>
    <div class="toolbar">
      <span class="rate">
        {{ parseInt(zoomSize.current * 100)}}%
      </span>
      <span class="v-line"></span>
      <span class="item" @click.stop="toolbarFUN('zoomAdd')" title="放缩比例+10%">放大</span>
      <span class="item" @click.stop="toolbarFUN('zoomReduce')" title="放缩比例-10%">缩小</span>
      <span class="v-line"></span>
      <span class="item" @click.stop="toolbarFUN('clear')" title="清空整个画布">清空</span>
      <span class="v-line"></span>
      <span class="item" @click.stop="toolbarFUN('undo')">撤销</span>
      <span class="item" @click.stop="toolbarFUN('redo')">重做</span>
      <span class="v-line"></span>
      <span class="item" @click.stop="toolbarFUN('save')" title="示例:保存图形化数据至localStorage">保存</span>
      <span class="item" @click.stop="toolbarFUN('png')" title="导出为图片">导出</span>
    </div>
    <div id="content" class="content">
      <div id="stencil" class="shapes"></div>
      <div id="container" class="x6-graph"></div>
      <div id="minimap" class="minimap"></div>
    </div>
  </div>
</template>

<script>
  import { Graph, Addon, DataUri, Vector } from '@antv/x6'
  export default {
    name: 'HelloWorld',
    data () {
      return {
        graph: Graph,
        stencil: Addon.Stencil,
        zoomSize: {
          min: 0.1,
          max: 2.0,
          current: 1.0
        },
        color: {
          none: 'none',
          black: '#000',
          white: '#fff',
          ports: '#31d0c6'
        },
        keyboard: {
          ctrl: false
        }
      }
    },
    mounted() {
      const graph = this.initX6FUN();
      this.initRegisterNodeFUN();
      this.initStencilFUN(graph);
      this.initShapeFUN(graph);
      this.initEventFUN(graph);
      this.initKeyboardFUN(graph);
      this.renderDataFUN(graph);
      this.autoSizeFUN(graph);
    },
    methods: {
      initX6FUN(){
        const container = document.getElementById('container');
        const width =  container.offsetWidth;
        const height = container.offsetHeight;
        const graph = new Graph({
          container: container,
          width,
          height,
          clipboard: true, // 剪切板，默认禁用。
          resizing: {
            enabled: (arg) =>{
              return arg.shape === 'process-node' && this.keyboard.ctrl
            },
            minWidth: 16,
            minHeight: 24,
            maxWidth: 300,
            maxHeight: 200,
          }, // 缩放节点，默认禁用。
          selecting: {
            enabled: true,
            rubberband: true,
            showNodeSelectionBox: true,
            showEdgeSelectionBox: true,
          }, // 点选/框选，默认禁用
          snapline: true, // 对齐线
          keyboard: true, // 键盘快捷键，默认禁用
          grid: {
            size: 10,
            visible: true,
            type: 'doubleMesh',
            args: [
              {
                color: '#E7E8EA',
                thickness: 1,
              },
              {
                color: '#CBCED3',
                thickness: 1,
                factor: 5,
              },
            ],
          }, // 网格
          onToolItemCreated({ name, cell, tool }) {
            if (name === 'vertices') {
              const options = tool.options;
              if (options && options.index % 2 === 1) {
                tool.setAttrs({ fill: 'red' })
              }
            }
          },
          panning: {
            enabled: true,
            eventTypes: ['leftMouseDown', 'rightMouseDown', 'mouseWheel'],
            modifiers: 'ctrl',
          },
          mousewheel: {
            enabled: true,
            zoomAtMousePosition: true, // 是否将鼠标位置作为中心缩放，默认为 true。
            modifiers: 'ctrl',
            minScale: 0.1,
            maxScale: 2,
          }, // 鼠标滚轮的默认行为是滚动页面
          history: {
            enabled: true,
            beforeAddCommand(event, args){
              // 忽略节点和边tool的历史变更
              if (args.key === 'tools' || args.key === 'zIndex') {
                return false
              }
              if(args.key === undefined && graph.isEdge(args.cell)){
                args.cell.removeTools();
              }
            }
          }, // 撤销/重做，默认禁用。
          connecting: {
            snap: {
              radius: 20,
            }, // 连线的过程中距离节点或者连接桩 20px 时会触发自动吸附
            allowMulti: false, // 是否允许在相同的起始节点和终止之间创建多条边
            allowBlank: false, // 是否允许连接到画布空白位置的点
            allowLoop: false, // 是否允许创建循环连线
            allowNode: false, // 是否允许边链接到节点
            allowPort: true, // 是否允许边链接到链接桩
            highlight: true, // 拖动边时，是否高亮显示所有可用的连接桩或节点
            router: 'manhattan', // 路由将边的路径点 vertices 做进一步转换处理，并在必要时添加额外的点，然后返回处理后的点
            connector: {
              name: 'rounded',
              args: {
                radius: 8,
              },
            }, // 连接器将起点、路由返回的点、终点加工为 元素的 d 属性，决定了边渲染到画布后的样式，默认值为 normal
            onToolItemCreated({ tool }) {
              const options = tool.options;
              if (options && options.index % 2 === 1) {
                tool.setAttrs({ fill: 'red' })
              }
            }, // 当工具项渲染完成时触发的回调。
            validateMagnet({magnet}) { // 点击 magnet 时 根据返回值来判断是否新增边，触发时机是 magnet 被按下，如果返回 false，则没有任何反应，如果返回 true，会在当前 magnet 创建一条新的边
              return magnet.getAttribute('port-group') !== 'in'
            },
            validateConnection({ sourceMagnet, targetMagnet }) { // 在移动边的时候判断连接是否有效，如果返回 false，当鼠标放开的时候，不会连接到当前元素，否则会连接到当前元素。
              if (!sourceMagnet || sourceMagnet.getAttribute('port-group') === 'in') {
                return false
              }
              // 只能连接到输入链接桩
              return targetMagnet && (targetMagnet.getAttribute('port-group') === 'in' || targetMagnet.getAttribute('port-group') === 'all');
            },
          }, // 连线选项
          highlighting: {
            magnetAdsorbed: {
              name: 'stroke',
              args: {
                padding: 12,
                attrs: {
                  opacity: 0.3,
                  'stroke-width': 10,
                  stroke: this.color.ports,
                  fill: this.color.white,
                },
              },
            },
          }, // 高亮选项
          minimap: {
            enabled: true,
            container: document.getElementById('minimap'),
            width: 198,
            height: 198,
            padding: 10,
            graphOptions: {
              async: true,
              createCellView(cell) {
                // 在小地图中不渲染边
                if (cell.isEdge()) {
                  return null
                }
              },
            }
          }, // 启用 Scroller 后可开启小地图，小地图是完整画布的预览，支持通过平移缩放小地图的视口来平移缩放画布。
        });

        this.graph = graph;
        return graph;
      },

      autoSizeFUN(graph){
        const resizeFn = () => {
          const content = document.getElementById('content');
          let width =  content.offsetWidth;
          let height = content.offsetHeight;
          graph.resize(width, height)
        };
        window.addEventListener('resize', resizeFn)
      },

      initRegisterNodeFUN(){
        /*
         水平对齐 textAnchor: 'middle',
         垂直对齐 textVerticalAnchor: 'middle',
         magnet 属性为 true 时，表示该元素可以被链接，即在连线过程中可以被当做连线的起点或终点，与链接桩类似。
         */
        const nodeConfig = {
          start: {
            height: 48,
            width: 48,
            strokeWidth: 2,
            text: '',
            textY: 48.6
          },
          end: {
            height: 48,
            width: 48,
            strokeWidth: 4,
            text: '',
            textY: 48.6
          },
          and: {
            height: 48,
            width: 48,
            text: '',
            points: '24,12 24,36 24,24 12,24 36,24',
            icon: '+',
            textY: 48.6
          },
          or: {
            height: 48,
            width: 48,
            text: '',
            icon: '×',
            points: '16,16 32,32 24,24 16,32 32,16',
            textY: 48.6
          }
        };
        const portConfig = {
          circle: {
            magnet: true,
            r: 4,
            stroke: this.color.ports,
            strokeWidth: 1,
            fill: this.color.white,
            style: {
              visibility: 'hidden',
            },
          }
        };
        const geStartEnd = (nodeConfig, shape) => {
          return {
            inherit: 'circle',
            width: nodeConfig.width,
            height: nodeConfig.height,
            attrs: {
              body: {
                strokeWidth: nodeConfig.strokeWidth,
              },
              nodeName: {
                fontSize: 12,
                text: nodeConfig.text,
                x: 0,
                y: nodeConfig.textY,
                refX:0.5,
                textAnchor: 'middle',
                textVerticalAnchor: 'middle',
              },
            },
            markup: [
              {
                tagName: 'circle',
                selector: 'body',
              },
              {
                tagName: 'text',
                selector: 'nodeName',
              },
            ],
            ports: {
              groups: {
                in: {
                  position: 'ellipseSpread',
                  attrs: {
                    circle: portConfig.circle,
                  },
                  zIndex: 8
                },
                out: {
                  position: 'ellipseSpread',
                  attrs: {
                    circle: portConfig.circle,
                  }
                },
              },
              items: [
                {
                  id: 'port1',
                  group: shape === 'start'? 'out' : 'in',
                  zIndex: 8,
                },
                {
                  id: 'port2',
                  group: shape === 'start'? 'out' : 'in',
                },
                {
                  id: 'port3',
                  group: shape === 'start'? 'out' : 'in',
                },
                {
                  id: 'port4',
                  group: shape === 'start'? 'out' : 'in',
                },
              ]
            },
          }
        };
        const getGateway = (nodeConfig) => {
          return {
            inherit: 'polygon',
            width: nodeConfig.width,
            height: nodeConfig.height,
            attrs: {
              body: {
                refPoints: '0,24 24,0 48,24 24,48',
              },
              icon: {
                stroke: this.color.black,
                fontSize: 12,
                text: nodeConfig.icon,
              },
              nodeName: {
                fontSize: 12,
                text: nodeConfig.text,
                x: 0,
                y: nodeConfig.textY,
                refX:0.5,
                textAnchor: 'middle',
                textVerticalAnchor: 'middle',
              },
            },
            markup: [
              {
                tagName: 'polygon',
                selector: 'body',
              },
              {
                tagName: 'text',
                selector: 'icon',
              },
              {
                tagName: 'text',
                selector: 'nodeName',
              },
            ],
            ports: {
              groups: {
                all: {
                  position: 'absolute',
                  attrs: {
                    circle: portConfig.circle,
                  }
                }
              },
              items: [
                {
                  id: 'port1',
                  group: 'all',
                  args: {
                    x: '0%',
                    y: '50%',
                  },
                },
                {
                  id: 'port2',
                  group: 'all',
                  args: {
                    x: '50%',
                    y: '0%',
                  },
                },
                {
                  id: 'port3',
                  group: 'all',
                  args: {
                    x: '100%',
                    y: '50%',
                  },
                },
                {
                  id: 'port4',
                  group: 'all',
                  args: {
                    x: '50%',
                    y: '100%',
                  },
                },
              ]
            },
          }
        };

        // 注册开始节点
        Graph.registerNode('start-node', geStartEnd(nodeConfig.start, 'start'), true);
        // 注册结束节点
        Graph.registerNode('end-node', geStartEnd(nodeConfig.end, 'end'), true);
        // 注册且网关
        Graph.registerNode('and-node', getGateway(nodeConfig.and), true);
        // 注册或网关
        Graph.registerNode('or-node',getGateway(nodeConfig.or), true);
        // 注册任务节点
        Graph.registerNode('process-node',{
          inherit: 'rect',
          width: 96,
          height: 48,
          attrs: {
            body: {
              stroke: this.color.black,
              rx: 5,
              ry: 5,
            },
            nodeName: {
              fontSize: 12,
              text: '',
              x: 0,
              y: 48,
              refX:0.5,
              textAnchor: 'middle',
              textVerticalAnchor: 'middle',
            },
          },
          markup: [
            {
              tagName: 'rect',
              selector: 'body',
            },
            {
              tagName: 'text',
              selector: 'nodeName',
            },
          ],
          ports: {
            groups: {
              all: {
                position: 'absolute',
                attrs: {
                  circle: portConfig.circle,
                },
              }
            },
            items: [
              {
                id: 'port1',
                group: 'all',
                args: {
                  x: '0%',
                  y: '50%',
                },
              },
              {
                id: 'port2',
                group: 'all',
                args: {
                  x: '50%',
                  y: '0%',
                },
              },
              {
                id: 'port3',
                group: 'all',
                args: {
                  x: '100%',
                  y: '50%',
                },
              },
              {
                id: 'port4',
                group: 'all',
                args: {
                  x: '50%',
                  y: '100%',
                },
              },
            ]
          },
        }, true);
        // 注册通知节点
        Graph.registerNode('notice-node',{
          inherit: 'circle',
          width: 48,
          height: 48,
          attrs: {
            body: {
            },
            icon1: {
              fill: this.color.none,
              stroke: this.color.black,
              refX: 13,
              refY: 17,
              points: '0,14 0,0 22,0 22,14',
            },
            icon2: {
              fill: this.color.none,
              stroke: this.color.black,
              refX: 13,
              refY: 17,
              points: '0,0 11,7 22,0',
            },
            icon3: {
              fill: this.color.none,
              stroke: this.color.black,
              refX: 13,
              refY: 17,
              points: '8,5 0,14',
            },
            icon4: {
              fill: this.color.none,
              stroke: this.color.black,
              refX: 13,
              refY: 17,
              points: '22,14 14,5',
            },
            nodeName: {
              fontSize: 12,
              text: '',
              x: 0,
              y: 48,
              refX:0.5,
              textAnchor: 'middle',
              textVerticalAnchor: 'middle',
            },
          },
          markup: [
            {
              tagName: 'circle',
              selector: 'body',
            },
            {
              tagName: 'polygon',
              selector: 'icon1',
            },
            {
              tagName: 'polyline',
              selector: 'icon2',
            },
            {
              tagName: 'polyline',
              selector: 'icon3',
            },
            {
              tagName: 'polyline',
              selector: 'icon4',
            },
            {
              tagName: 'text',
              selector: 'nodeName',
            },
          ],
          ports: {
            groups: {
              all: {
                position: 'absolute',
                attrs: {
                  circle: portConfig.circle,
                },
              }
            },
            items: [
              {
                id: 'port1',
                group: 'all',
                args: {
                  x: '0%',
                  y: '50%',
                },
              },
              {
                id: 'port2',
                group: 'all',
                args: {
                  x: '50%',
                  y: '0%',
                },
              },
              {
                id: 'port3',
                group: 'all',
                args: {
                  x: '100%',
                  y: '50%',
                },
              },
              {
                id: 'port4',
                group: 'all',
                args: {
                  x: '50%',
                  y: '100%',
                },
              },
            ]
          },
        }, true);
      },

      initStencilFUN(graph) {
        this.stencil = new Addon.Stencil({
          title: '图形元素',
          target: graph,
          stencilGraphWidth: 170,
          stencilGraphHeight: document.body.offsetHeight - 105,
          layoutOptions: {
            columns: 3,
            columnWidth: 48,
            rowHeight: 40,
            marginY: 20,
          },
          getDropNode(node) {
            let n = 2;
            if(node.shape === 'process-node'){
              n = 3;
            } else if(node.shape === 'and-node' || node.shape === 'or-node') {
              node.attrs.icon.fontSize = 24;
            } else if(node.shape === 'notice-node') {
              node.attrs.icon1.refX=13;
              node.attrs.icon1.refY=17;
              node.attrs.icon1.points='0,14 0,0 22,0 22,14';

              node.attrs.icon2.refX=13;
              node.attrs.icon2.refY=17;
              node.attrs.icon2.points='0,0 11,7 22,0';

              node.attrs.icon3.refX=13;
              node.attrs.icon3.refY=17;
              node.attrs.icon3.points='8,5 0,14';

              node.attrs.icon4.refX=13;
              node.attrs.icon4.refY=17;
              node.attrs.icon4.points='22,14 14,5';
            }
            const size = node.size();
            return node.clone().size(size.width * n, size.height * n)
          },
        });
        const stencilContainer = document.querySelector('#stencil');
        if (stencilContainer) {
          stencilContainer.appendChild(this.stencil.container)
        }
      },

      initShapeFUN(graph) {
        const r1 = graph.createNode({
          shape: 'start-node',
          width: 24,
          height: 24,
        });
        const r2 = graph.createNode({
          shape: 'end-node',
          width: 24,
          height: 24,
        });
        const r3 = graph.createNode({
          shape: 'notice-node',
          width: 24,
          height: 24,
          attrs: {
            icon1: {
              refX: 6.5,
              refY: 8.5,
              points: '0,7 0,0 11,0 11,7',
            },
            icon2: {
              refX: 6.5,
              refY: 8.5,
              points: '0,0 5.5,3.5 11,0',
            },
            icon3: {
              refX: 6.5,
              refY: 8.5,
              points: '4,2.5 0,7',
            },
            icon4: {
              refX: 6.5,
              refY: 8.5,
              points: '11,7 7,2.5',
            },
          }
        });
        const r4 = graph.createNode({
          shape: 'or-node',
          width: 24,
          height: 24,
        });
        const r5 = graph.createNode({
          shape: 'and-node',
          width: 24,
          height: 24,
        });
        const r6 = graph.createNode({
          shape: 'process-node',
          width: 24,
          height: 16,
        });
        this.stencil.load([r1, r2, r3, r4, r5, r6])
      },

      togglePorts(container, show) {
        const ports = container.querySelectorAll(
          '.x6-port-body',
        );
        for (let i = 0, len = ports.length; i < len; i = i + 1) {
          ports[i].style.visibility = show ? 'visible' : 'hidden'
        }
      },

      initKeyboardFUN(graph){
        graph.bindKey('ctrl', () => {
          if(!this.keyboard.ctrl){
            this.keyboard.ctrl = true;
          }
          return false
        }, 'keydown');
        graph.bindKey('ctrl', () => {
          if(this.keyboard.ctrl){
            this.keyboard.ctrl = false;
          }
          return false
        }, 'keyup');
        graph.bindKey('ctrl+c', () => {
          const cells = graph.getSelectedCells();
          if (cells.length > 0) {
            graph.copy(cells);
          }
          return false
        });
        graph.bindKey('ctrl+v', () => {
          if (!graph.isClipboardEmpty()) {
            const cells = graph.paste();
            graph.cleanSelection();
            graph.select(cells)
          }
          return false
        });
        graph.bindKey(['Delete', 'Backspace'], () => {
          const cells = graph.getSelectedCells();
          cells.forEach(e => {
            graph.removeNode(e);
          });
          return false
        }, 'keyup');
        graph.bindKey('ctrl+z', () => {
          this.graph.history.undo();
          return false
        }, 'keyup');
        graph.bindKey('ctrl+y', () => {
          this.graph.history.redo();
          return false
        }, 'keyup')
      },

      initEventFUN(graph) {
        const container = document.getElementById('container');

        graph.on('node:mouseenter', () => {
            this.togglePorts(container, true);
        });

        graph.on('node:mouseleave', () => {
          this.togglePorts(container, false);
        });

        graph.on('edge:change:*', ({ options }) => {
          if(options.toolId){
            this.togglePorts(container, true);
          }
        });

        graph.on('edge:connected', ({ isNew, edge }) => {
          // 非新建的边
          if(!isNew){
            this.togglePorts(container, false);
          }
        });

        graph.on('edge:mouseenter', ({ cell }) => {
          let tools = [
            {
              name: 'source-arrowhead',
              args: {
                tagName: 'rect',
                attrs: {
                  fill: this.color.black,
                  stroke: this.color.none,
                  y: -1,
                  width: 11,
                  height: 2,
                },
              },
            },
            {
              name: 'target-arrowhead',
              args: {
                attrs: {
                  'stroke-width': 1,
                  fill: this.color.ports,
                  d: 'M -12 -8 3 0 -12 8 -9 0 Z',
                },
              },
            },
          ];
          if(this.keyboard.ctrl){
            tools.push({
              name: 'vertices'
            });
          }
          cell.addTools(tools);
        });

        graph.on('edge:mouseleave', ({ cell }) => {
          cell.removeTools()
        });

        graph.on('blank:click', () => {
          this.togglePorts(container, false);
          let edges = graph.getEdges();
          if(edges.length > 0){
            edges.forEach(e => {
              e.removeTools()
            })
          }
        })
      },

      toolbarFUN(type){
        switch (type) {
          case 'zoomAdd': {
            if(this.isBigFUN(this.zoomSize.max, this.zoomSize.current)){
              this.zoomSize.current += 0.1;
              this.graph.zoomTo(this.zoomSize.current);
            }
          }break;
          case 'zoomReduce': {
            if(this.isBigFUN(this.zoomSize.current, this.zoomSize.min)){
              this.zoomSize.current -= 0.1;
              this.graph.zoomTo(this.zoomSize.current);
            }
          }break;
          case 'clear': {
            this.graph.clearCells();
          }break;
          case 'undo': {
            this.graph.history.undo();
          }break;
          case 'redo': {
            this.graph.history.redo();
          }break;
          case 'save': {
            const json = this.graph.toJSON();
            if(json.cells.length === 0){
              localStorage.removeItem("graphData");
            } else {
              const data = JSON.stringify(json);
              localStorage.setItem("graphData", data);
              alert('已保存,请通过控制台输出查看详情');
              console.log('保存数据', data);
            }
          }break;
          case 'png': {
            this.graph.toPNG((dataUri) => {
              DataUri.downloadDataUri(dataUri, 'chart.png')
            })
          }break;
        }
      },

      renderDataFUN(graph){
        const json = localStorage.getItem("graphData");
        if(json){
          const data = JSON.parse(json);
          alert("从localStorage获取到数据!",data);
          console.log("按照指定的 JSON 数据渲染节点和边",data);
          this.graph.fromJSON(data);
        }
      },

      isBigFUN(num1, num2){
        return (num1 - num2) > 0.0001
      }
    }
  }
</script>

<style lang="scss">

  .x6-widget-snapline-vertical{
     border-right: 1px solid #bcbabd !important;
   }
  .x6-widget-snapline-horizontal{
    border-bottom: 1px solid #bcbabd !important;
  }

  .x6-vue-wrap {
    width: 100%;
    height: 100%;
    overflow-y: hidden;
    .header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      height: 65px;
      padding-left: 16px;
      padding-right: 32px;
      background: #fafbfc;
      border-bottom: 1px solid #dfe3e8;
      position: relative;
      color: rgba(0, 0, 0, 0.45);
      .text {
        font-size: 24px;
      }
    }
    .toolbar {
      display: flex;
      align-items: center;
      margin-left: 8px;
      font-size: 12px;
      height: 40px;
      background-color: #fafbfc;
      border-bottom: 1px solid #dfe3e8;
      user-select: none;
      span.v-line{
        display: inline-block;
        height: 16px;
        margin: 0 4px;
        width: 1px;
        background-color: #bcbcbc;
      }
      span.rate{
        display: inline-block;
        width: 34px;
        text-align: center;
        color: grey;
      }
      span.item{
        cursor: pointer;
        padding: 2px 8px;
        &:hover{
          background-color: #e0e0e0;
          border-radius: 5px;
        }
      }
    }
    .content {
      display: flex;
      flex-flow:row;
      height: calc(100% - 105px);
      position: relative;
      .shapes {
        position: relative;
        width: 170px;
        border-right: 1px solid #dfe3e8;
        .x6-widget-stencil .x6-widget-stencil-content{
          overflow-y: hidden;
        }
      }
      #container {
        height: 100%;
        flex:1;
        box-sizing: border-box;
      }
      .minimap {
        position: fixed;
        right: 20px;
        bottom: 20px;
        width: 200px;
        height: 200px;
        border: 1px solid #ccc;
        border-radius: 4px;
        background-color: #fafbfc;
      }
    }
    @media screen  and (max-height: 350px) {
      .minimap {
        visibility: hidden;
      }
    }
  }

  .x6-widget-stencil {
    background-color: #fff;
    .x6-widget-stencil-title {
      background-color: #fff;
    }
  }

  // resizing
  .x6-widget-transform {
    margin: -1px 0 0 -1px;
    padding: 0;
    border: 1px solid #239edd;
    > div {
      border: 1px solid #239edd;
    }
    > div:hover {
      background-color: #3dafe4;
    }
    .x6-widget-transform-active-handle {
      background-color: #3dafe4;
    }
  }
  .x6-widget-transform-resize {
    border-radius: 0;
  }

  // selection
  .x6-widget-selection-inner {
    border: 1px solid #239edd;
  }
  .x6-widget-selection-box {
    opacity: 1;
    border: 1px dashed #239edd;
  }

  // snapline
  .x6-widget-snapline-vertical {
    border-right-color: #239edd;
  }
  .x6-widget-snapline-horizontal {
    border-bottom-color: #239edd;
  }

  // minimap
  .x6-widget-minimap-viewport,
  .x6-widget-minimap-viewport-zoom {
    border: 2px solid #3371dd;
  }

</style>
